use std::sync::atomic::{AtomicUsize,Ordering};
use crate::dynf::ast::BoolOperator;
use crate::dynf::code::{CodeObject,Instruction,BinaryOperator,Label};
use crate::dynf::vm::{VM,Scope};
use crate::dynf::vm::{DObject,DObjectRef};
use std::sync::{Mutex,Arc};

pub struct Frame {
    code:Arc<Box<CodeObject>>,
    lasti:usize,
    state: Mutex<Box<FrameState> >,
    scope:Scope,
}

impl Frame {
    pub fn new(code:Arc<Box<CodeObject>>,scope:Scope) -> Frame {
        Frame {
            code,
            lasti:0,
            state:Mutex::new(Box::new(FrameState {
                stack:vec![],
                blocks:vec![]
            }) ),
            scope
        }
    }

    pub fn run(&self,vm:&VM) -> Option<DObjectRef> {
        println!("start frame run {:?}",&self.code);
       
        let mut state = self.state.lock().unwrap();
        let mut exec = Box::new(ExecutingFrame {
            code:&self.code,
            lasti:self.lasti,
            state:&mut state,
            scope:&self.scope
        });
        exec.run(vm)
    }

   
}

struct ExecutingFrame<'a> {
    code: &'a CodeObject,
    lasti: usize,
    state: &'a mut FrameState,
    scope: &'a Scope
}

struct FrameState {
    pub stack: Vec<DObjectRef>,
    pub blocks:Vec<Block>
}

#[derive(Clone, Debug)]
struct Block {
    typ: BlockType,
    level: usize,
}

#[derive(Clone, Debug)]
enum BlockType {
    Loop {
        start: Label,
        end: Label,
    }
}

#[derive(Clone, Debug)]
enum UnwindReason {
    Returning { value: DObject },
    Break,
    Continue,
}

impl ExecutingFrame<'_> {
    fn run(&mut self, vm: &VM) -> Option<DObjectRef>  {
        loop {
            let result = self.execute_instruction(vm);
            //println!("stack: {:?}",self.state.stack);
            match result {
                Ok(None) => {},
                Ok(Some(val)) => {
                    break Some(val);
                },
                Err(err) => {

                }
            };
            if self.lasti >= self.code.instructions.len() {
                break None;
            }
        }
    }

    fn execute_instruction(&mut self, vm: &VM) -> Result<Option<DObjectRef>,()> {
        let instruction = &self.code.instructions[self.lasti];
        self.lasti += 1;
        println!("{:?}",instruction);
        match instruction {
            Instruction::LoadConst(value) => {
                let object:DObjectRef =  DObject::from(value).into_ref();
                self.state.stack.push(object);
              
                Ok(None)
            },
            Instruction::BinaryOperation(op) => {
                self.execute_binop(op, vm);
                Ok(None)
            },
            Instruction::StoreName(sym_name) => {
                self.store_name(vm,sym_name);
            
                Ok(None)
            },
            Instruction::LoadName(load_name) => {
                self.load_name(vm, load_name);
                Ok(None)
            },
            Instruction::JumpIfFalse(target) => {
                let obj = self.pop_value();
                let b = obj.cast_bool();
                if !b {
                    self.jump(target);
                }
                Ok(None)
            },
            Instruction::Jump(target) => {
                self.jump(target);
                Ok(None)
            },
            Instruction::Boolperation(op) => {
                self.execute_boolop(op, vm);
                Ok(None)
            },
            Instruction::MakeFunction => {
                self.execute_make_function(vm);
                Ok(None)
            },
            Instruction::CallFunction(count) => {
                self.execute_call_function(*count, vm);
                Ok(None)
            },
            Instruction::ReturnValue => {
                let value = self.pop_value();
               
                Ok(Some(value))
            },
            Instruction::SetupLoop {start,end} => {
                self.push_block(BlockType::Loop {
                    start: *start,
                    end: *end,
                });
                Ok(None)
            },
            Instruction::PopBlock => {
                self.pop_block();
                Ok(None)
            },
            Instruction::Break => {
                self.unwind_blocks(vm,UnwindReason::Break)
            },
            Instruction::Continue => {
                self.unwind_blocks(vm, UnwindReason::Continue)
            },
            _ => Err(())
        }
        
    }

    fn unwind_blocks(&mut self, vm: &VM, reason: UnwindReason) -> Result<Option<DObjectRef>,()> {
        while let Some(block) = self.current_block() {
            match block.typ {
                BlockType::Loop {start,end} => match &reason {
                    UnwindReason::Break => {
                        self.pop_block();
                        self.jump(&end);
                        return Ok(None);
                    }
                    UnwindReason::Continue => {
                        self.jump(&start);
                        return Ok(None);
                    }
                    _ => {
                        self.pop_block();
                    }
                },
                _ => { self.pop_block(); }
            }
        };
        Ok(None)
    }

    fn execute_make_function(&mut self, vm: &VM) {
        let _name = self.pop_value();
        let code = self.pop_value();
        
        let clone_scope = self.scope.clone();
        let code_object = code.cast_clone_code();
        let new_funcobject = DObject::new_func(code_object, clone_scope);
      
        self.push_value(Arc::new(new_funcobject));
    }

    fn execute_call_function(&mut self,count:usize,vm: &VM) {
        let args = self.pop_multiple(count);
        let func_ref = self.pop_value();
        if let Some(ret) = vm.invoke(&func_ref, args) {
            self.push_value(ret);
        }
    }

    fn store_name(&mut self,vm:&VM,name:&String) {
        let obj = self.pop_value();
        println!("store_name: {:?}",obj);
        self.scope.store_name(vm, name.as_str(), obj);   
    }

    fn load_name(&mut self,vm:&VM,name:&String) {
        if let Some(val) = self.scope.load_name(vm, name.as_str()) {
            self.push_value(val)
        } else {
            panic!("name {:?} is not defined",name);
        }
    }

    fn execute_binop(&mut self,op:&BinaryOperator,vm:&VM) {
        let b_ref = self.pop_value();
        let a_ref = self.pop_value();
        let new_val = match op {
            BinaryOperator::Add => vm._op_add(a_ref, b_ref),
            BinaryOperator::Multiply => vm._op_mul(a_ref, b_ref),
            BinaryOperator::Divide => vm._op_div(a_ref, b_ref),
            BinaryOperator::Subtract => vm._op_sub(a_ref, b_ref),
            _ => unimplemented!()
        };
        self.push_value(new_val);
    }

    fn execute_boolop(&mut self,op:&BoolOperator,vm:&VM) {
        let b_ref = self.pop_value();
        let a_ref = self.pop_value();
        let new_val = match op {
           BoolOperator::Eq => {
               vm._eq(a_ref, b_ref)
           },
           BoolOperator::Greater => {
            a_ref.into_float() > b_ref.into_float()
           },
           BoolOperator::Less => {
                a_ref.into_float() < b_ref.into_float()
           },
           BoolOperator::GreaterEq => {
            a_ref.into_float() >= b_ref.into_float()
           },
           BoolOperator::LessEq => {
            a_ref.into_float() <= b_ref.into_float()
           },
            _ => unimplemented!()
        };
        self.push_value(  Arc::new(DObject::new_bool(new_val)) );
    }

    fn jump(&mut self, label: &Label) {
        let target_pc = self.code.label_map[&label];
        self.lasti = target_pc;
    }

    fn pop_value(&mut self) -> DObjectRef {
        self.state.stack.pop().expect("Tried to pop value but there was nothing on the stack")
    }

    fn push_value(&mut self, obj: DObjectRef) {
        self.state.stack.push(obj);
    }

    fn pop_multiple(&mut self, count: usize) -> Vec<DObjectRef> {
        let stack_len = self.state.stack.len();
        self.state.stack.drain(stack_len - count..stack_len).collect()
    }

    fn push_block(&mut self, typ: BlockType) {
        self.state.blocks.push(Block {
            typ,
            level: self.state.stack.len(),
        });
    }

    fn pop_block(&mut self) -> Block {
        let block = self.state.blocks.pop().expect("No more blocks to pop!");
        self.state.stack.truncate(block.level);
        block
    }

    fn current_block(&self) -> Option<Block> {
        self.state.blocks.last().cloned()
    }
    
}